﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Agile.NetWork.Serial;

namespace Agile.NetWork.Profinet.Melsec
{
    /// <summary>
    /// 三菱PLC的计算机链接协议，适用的PLC型号参考备注
    /// </summary>
    /// <remarks>
    /// <list type="table">
    ///     <listheader>
    ///         <term>系列</term>
    ///         <term>是否支持</term>
    ///         <term>备注</term>
    ///     </listheader>
    ///     <item>
    ///         <description>FX3UC系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX3U系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX3GC系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX3G系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX3S系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX2NC系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX2N系列</description>
    ///         <description>部分支持(v1.06+)</description>
    ///         <description>通过监控D8001来确认版本号</description>
    ///     </item>
    ///     <item>
    ///         <description>FX1NC系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX1N系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX1S系列</description>
    ///         <description>支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX0N系列</description>
    ///         <description>部分支持(v1.20+)</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX0S系列</description>
    ///         <description>不支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX0系列</description>
    ///         <description>不支持</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX2C系列</description>
    ///         <description>部分支持(v3.30+)</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX2(FX)系列</description>
    ///         <description>部分支持(v3.30+)</description>
    ///         <description></description>
    ///     </item>
    ///     <item>
    ///         <description>FX1系列</description>
    ///         <description>不支持</description>
    ///         <description></description>
    ///     </item>
    /// </list>
    /// </remarks>
    public class MelsecFxLinks : SerialDeviceBase<RegularByteTransform>
    {
        #region Constructor

        /// <summary>
        /// 实例化默认的构造方法
        /// </summary>
        public MelsecFxLinks()
        {
            WordLength = 1;
        }

        #endregion

        #region Public Member

        /// <summary>
        /// PLC的站号信息
        /// </summary>
        public byte Station
        {
            get { return station; }
            set { station = value; }
        }

        /// <summary>
        /// 报文等待时间，单位10ms，设置范围为0-15
        /// </summary>
        public byte WaittingTime
        {

            get { return watiingTime; }
            set
            {
                if (watiingTime > 0x0F)
                {
                    watiingTime = 0x0F;
                }
                else
                {
                    watiingTime = value;
                }
            }
        }

        /// <summary>
        /// 是否启动和校验
        /// </summary>
        public bool SumCheck
        {
            get { return sumCheck; }
            set { sumCheck = value; }
        }

        #endregion

        #region Read Write Support

        /// <summary>
        /// 批量读取PLC的数据，以字为单位，支持读取X,Y,M,S,D,T,C，具体的地址范围需要根据PLC型号来确认
        /// </summary>
        /// <param name="address">地址信息</param>
        /// <param name="length">数据长度</param>
        /// <returns>读取结果信息</returns>
        public override Result<byte[]> Read(string address, ushort length)
        {
            // 解析指令
            Result<byte[]> command = BuildReadCommand(this.station, address, length, false, sumCheck, watiingTime);
            if (!command.Success) return Result.CreateFailedResult<byte[]>(command);

            // 核心交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return Result.CreateFailedResult<byte[]>(read);

            // 结果验证
            if (read.Data[0] != 0x02) return new Result<byte[]>(read.Data[0], "Read Faild:" + Utils.ByteToHexString(read.Data, ' '));

            // 提取结果
            byte[] Data = new byte[length * 2];
            for (int i = 0; i < Data.Length / 2; i++)
            {
                ushort tmp = Convert.ToUInt16(Encoding.ASCII.GetString(read.Data, i * 4 + 5, 4), 16);
                BitConverter.GetBytes(tmp).CopyTo(Data, i * 2);
            }
            return Result.CreateSuccessResult(Data);
        }

        /// <summary>
        /// 批量写入PLC的数据，以字为单位，也就是说最少2个字节信息，支持X,Y,M,S,D,T,C，具体的地址范围需要根据PLC型号来确认
        /// </summary>
        /// <param name="address">地址信息</param>
        /// <param name="value">数据值</param>
        /// <returns>是否写入成功</returns>
        public override Result Write(string address, byte[] value)
        {
            // 解析指令
            Result<byte[]> command = BuildWriteByteCommand(this.station, address, value, sumCheck, watiingTime);
            if (!command.Success) return command;

            // 核心交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 结果验证
            if (read.Data[0] != 0x06) return new Result(read.Data[0], "Write Faild:" + Utils.ByteToHexString(read.Data, ' '));

            // 提取结果
            return Result.CreateSuccessResult();
        }

        #endregion

        #region Bool Read Write

        /// <summary>
        /// 批量读取bool类型数据，支持的类型为X,Y,S,T,C，具体的地址范围取决于PLC的类型
        /// </summary>
        /// <param name="address">地址信息，比如X10,Y17，注意X，Y的地址是8进制的</param>
        /// <param name="length">读取的长度</param>
        /// <returns>读取结果信息</returns>
        public Result<bool[]> ReadBool(string address, ushort length)
        {
            // 解析指令
            Result<byte[]> command = BuildReadCommand(this.station, address, length, true, sumCheck, watiingTime);
            if (!command.Success) return Result.CreateFailedResult<bool[]>(command);

            // 核心交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return Result.CreateFailedResult<bool[]>(read);

            // 结果验证
            if (read.Data[0] != 0x02) return new Result<bool[]>(read.Data[0], "Read Faild:" + Utils.ByteToHexString(read.Data, ' '));

            // 提取结果
            byte[] buffer = new byte[length];
            Array.Copy(read.Data, 5, buffer, 0, length);
            return Result.CreateSuccessResult(buffer.Select(m => m == 0x31).ToArray());
        }

        /// <summary>
        /// 批量读取bool类型数据，支持的类型为X,Y,S,T,C，具体的地址范围取决于PLC的类型
        /// </summary>
        /// <param name="address">地址信息，比如X10,Y17，注意X，Y的地址是8进制的</param>
        /// <returns>读取结果信息</returns>
        public Result<bool> ReadBool(string address)
        {
            Result<bool[]> read = ReadBool(address, 1);
            if (!read.Success) return Result.CreateFailedResult<bool>(read);

            return Result.CreateSuccessResult(read.Data[0]);
        }

        /// <summary>
        /// 批量写入bool类型的数值，支持的类型为X,Y,S,T,C，具体的地址范围取决于PLC的类型
        /// </summary>
        /// <param name="address">PLC的地址信息</param>
        /// <param name="value">数据信息</param>
        /// <returns>是否写入成功</returns>
        public Result Write(string address, bool value)
        {
            return Write(address, new bool[] { value });
        }

        /// <summary>
        /// 批量写入bool类型的数组，支持的类型为X,Y,S,T,C，具体的地址范围取决于PLC的类型
        /// </summary>
        /// <param name="address">PLC的地址信息</param>
        /// <param name="value">数据信息</param>
        /// <returns>是否写入成功</returns>
        public Result Write(string address, bool[] value)
        {
            // 解析指令
            Result<byte[]> command = BuildWriteBoolCommand(this.station, address, value, sumCheck, watiingTime);
            if (!command.Success) return command;

            // 核心交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 结果验证
            if (read.Data[0] != 0x06) return new Result(read.Data[0], "Write Faild:" + Utils.ByteToHexString(read.Data, ' '));

            // 提取结果
            return Result.CreateSuccessResult();
        }

        #endregion

        #region Start Stop

        /// <summary>
        /// 启动PLC
        /// </summary>
        /// <returns>是否启动成功</returns>
        public Result StartPLC()
        {
            // 解析指令
            Result<byte[]> command = BuildStart(this.station, sumCheck, watiingTime);
            if (!command.Success) return command;

            // 核心交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 结果验证
            if (read.Data[0] != 0x06) return new Result(read.Data[0], "Start Faild:" + Utils.ByteToHexString(read.Data, ' '));

            // 提取结果
            return Result.CreateSuccessResult();
        }

        /// <summary>
        /// 停止PLC
        /// </summary>
        /// <returns>是否停止成功</returns>
        public Result StopPLC()
        {
            // 解析指令
            Result<byte[]> command = BuildStop(this.station, sumCheck, watiingTime);
            if (!command.Success) return command;

            // 核心交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 结果验证
            if (read.Data[0] != 0x06) return new Result(read.Data[0], "Stop Faild:" + Utils.ByteToHexString(read.Data, ' '));

            // 提取结果
            return Result.CreateSuccessResult();
        }

        #endregion

        #region Private Member

        private byte station = 0x00;                 // PLC的站号信息
        private byte watiingTime = 0x00;             // 报文的等待时间，设置为0-15
        private bool sumCheck = true;                // 是否启用和校验

        #endregion

        #region Static Helper


        /// <summary>
        /// 解析数据地址成不同的三菱地址类型
        /// </summary>
        /// <param name="address">数据地址</param>
        /// <param name="isBool">是否是位读取</param>
        /// <returns>地址结果对象</returns>
        private static Result<string> FxAnalysisAddress(string address, bool isBool)
        {
            var result = new Result<string>();
            try
            {
                if (isBool)
                {
                    switch (address[0])
                    {
                        case 'X':
                        case 'x':
                            {
                                ushort tmp = Convert.ToUInt16(address.Substring(1), 8);
                                result.Data = "X" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'Y':
                        case 'y':
                            {
                                ushort tmp = Convert.ToUInt16(address.Substring(1), 8);
                                result.Data = "Y" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'M':
                        case 'm':
                            {
                                result.Data = "M" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'S':
                        case 's':
                            {
                                result.Data = "S" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'T':
                        case 't':
                            {
                                result.Data = "TS" + Convert.ToUInt16(address.Substring(1), 10).ToString("D3");
                                break;
                            }
                        case 'C':
                        case 'c':
                            {
                                result.Data = "CS" + Convert.ToUInt16(address.Substring(1), 10).ToString("D3");
                                break;
                            }
                        default: throw new Exception("输入的类型不支持，请重新输入");
                    }
                }
                else
                {
                    switch (address[0])
                    {
                        case 'X':
                        case 'x':
                            {
                                ushort tmp = Convert.ToUInt16(address.Substring(1), 8);
                                result.Data = "X" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'Y':
                        case 'y':
                            {
                                ushort tmp = Convert.ToUInt16(address.Substring(1), 8);
                                result.Data = "Y" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'M':
                        case 'm':
                            {
                                result.Data = "M" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'S':
                        case 's':
                            {
                                result.Data = "S" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'T':
                        case 't':
                            {
                                result.Data = "TN" + Convert.ToUInt16(address.Substring(1), 10).ToString("D3");
                                break;
                            }
                        case 'C':
                        case 'c':
                            {
                                result.Data = "CN" + Convert.ToUInt16(address.Substring(1), 10).ToString("D3");
                                break;
                            }
                        case 'D':
                        case 'd':
                            {
                                result.Data = "D" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        case 'R':
                        case 'r':
                            {
                                result.Data = "R" + Convert.ToUInt16(address.Substring(1), 10).ToString("D4");
                                break;
                            }
                        default: throw new Exception("输入的类型不支持，请重新输入");
                    }
                }
            }
            catch (Exception ex)
            {
                result.Message = ex.Message;
                return result;
            }

            result.Success = true;
            return result;
        }

        /// <summary>
        /// 计算指令的和校验码
        /// </summary>
        /// <param name="data">指令</param>
        /// <returns>校验之后的信息</returns>
        public static string CalculateAcc(string data)
        {
            byte[] buffer = Encoding.ASCII.GetBytes(data);

            int count = 0;
            for (int i = 0; i < buffer.Length; i++)
            {
                count += buffer[i];
            }

            return data + count.ToString("X4").Substring(2);
        }

        /// <summary>
        /// 创建一条读取的指令信息，需要指定一些参数
        /// </summary>
        /// <param name="station">PLCd的站号</param>
        /// <param name="address">地址信息</param>
        /// <param name="length">数据长度</param>
        /// <param name="isBool">是否位读取</param>
        /// <param name="sumCheck">是否和校验</param>
        /// <param name="waitTime">等待时间</param>
        /// <returns>是否成功的结果对象</returns>
        public static Result<byte[]> BuildReadCommand(byte station, string address, ushort length, bool isBool, bool sumCheck = true, byte waitTime = 0x00)
        {
            Result<string> addressAnalysis = FxAnalysisAddress(address, isBool);
            if (!addressAnalysis.Success) return Result.CreateFailedResult<byte[]>(addressAnalysis);

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(station.ToString("D2"));
            stringBuilder.Append("FF");

            if (isBool)
                stringBuilder.Append("BR");
            else
                stringBuilder.Append("WR");

            stringBuilder.Append(waitTime.ToString("X"));
            stringBuilder.Append(addressAnalysis.Data);
            stringBuilder.Append(length.ToString("D2"));

            byte[] core = null;
            if (sumCheck)
                core = Encoding.ASCII.GetBytes(CalculateAcc(stringBuilder.ToString()));
            else
                core = Encoding.ASCII.GetBytes(stringBuilder.ToString());

            core = Utils.SpliceTwoByteArray(new byte[] { 0x05 }, core);

            return Result.CreateSuccessResult(core);
        }

        /// <summary>
        /// 创建一条别入bool数据的指令信息，需要指定一些参数
        /// </summary>
        /// <param name="station">站号</param>
        /// <param name="address">地址</param>
        /// <param name="value">数组值</param>
        /// <param name="sumCheck">是否和校验</param>
        /// <param name="waitTime">等待时间</param>
        /// <returns>是否创建成功</returns>
        public static Result<byte[]> BuildWriteBoolCommand(byte station, string address, bool[] value, bool sumCheck = true, byte waitTime = 0x00)
        {
            Result<string> addressAnalysis = FxAnalysisAddress(address, true);
            if (!addressAnalysis.Success) return Result.CreateFailedResult<byte[]>(addressAnalysis);

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(station.ToString("D2"));
            stringBuilder.Append("FF");
            stringBuilder.Append("BW");
            stringBuilder.Append(waitTime.ToString("X"));
            stringBuilder.Append(addressAnalysis.Data);
            stringBuilder.Append(value.Length.ToString("D2"));
            for (int i = 0; i < value.Length; i++)
            {
                stringBuilder.Append(value[i] ? "1" : "0");
            }

            byte[] core = null;
            if (sumCheck)
                core = Encoding.ASCII.GetBytes(CalculateAcc(stringBuilder.ToString()));
            else
                core = Encoding.ASCII.GetBytes(stringBuilder.ToString());

            core = Utils.SpliceTwoByteArray(new byte[] { 0x05 }, core);

            return Result.CreateSuccessResult(core);
        }

        /// <summary>
        /// 创建一条别入byte数据的指令信息，需要指定一些参数，按照字单位
        /// </summary>
        /// <param name="station">站号</param>
        /// <param name="address">地址</param>
        /// <param name="value">数组值</param>
        /// <param name="sumCheck">是否和校验</param>
        /// <param name="waitTime">等待时间</param>
        /// <returns>是否创建成功</returns>
        public static Result<byte[]> BuildWriteByteCommand(byte station, string address, byte[] value, bool sumCheck = true, byte waitTime = 0x00)
        {
            Result<string> addressAnalysis = FxAnalysisAddress(address, false);
            if (!addressAnalysis.Success) return Result.CreateFailedResult<byte[]>(addressAnalysis);

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(station.ToString("D2"));
            stringBuilder.Append("FF");
            stringBuilder.Append("WW");
            stringBuilder.Append(waitTime.ToString("X"));
            stringBuilder.Append(addressAnalysis.Data);
            stringBuilder.Append((value.Length / 2).ToString("D2"));

            // 字写入
            byte[] buffer = new byte[value.Length * 2];
            for (int i = 0; i < value.Length / 2; i++)
            {
                MelsecHelper.BuildBytesFromData(BitConverter.ToUInt16(value, i * 2)).CopyTo(buffer, 4 * i);
            }
            stringBuilder.Append(Encoding.ASCII.GetString(buffer));



            byte[] core = null;
            if (sumCheck)
                core = Encoding.ASCII.GetBytes(CalculateAcc(stringBuilder.ToString()));
            else
                core = Encoding.ASCII.GetBytes(stringBuilder.ToString());

            core = Utils.SpliceTwoByteArray(new byte[] { 0x05 }, core);

            return Result.CreateSuccessResult(core);
        }

        /// <summary>
        /// 创建启动PLC的报文信息
        /// </summary>
        /// <param name="station">站号信息</param>
        /// <param name="sumCheck">是否和校验</param>
        /// <param name="waitTime">等待时间</param>
        /// <returns>是否创建成功</returns>
        public static Result<byte[]> BuildStart(byte station, bool sumCheck = true, byte waitTime = 0x00)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(station.ToString("D2"));
            stringBuilder.Append("FF");
            stringBuilder.Append("RR");
            stringBuilder.Append(waitTime.ToString("X"));

            byte[] core = null;
            if (sumCheck)
                core = Encoding.ASCII.GetBytes(CalculateAcc(stringBuilder.ToString()));
            else
                core = Encoding.ASCII.GetBytes(stringBuilder.ToString());

            core = Utils.SpliceTwoByteArray(new byte[] { 0x05 }, core);

            return Result.CreateSuccessResult(core);
        }

        /// <summary>
        /// 创建启动PLC的报文信息
        /// </summary>
        /// <param name="station">站号信息</param>
        /// <param name="sumCheck">是否和校验</param>
        /// <param name="waitTime">等待时间</param>
        /// <returns>是否创建成功</returns>
        public static Result<byte[]> BuildStop(byte station, bool sumCheck = true, byte waitTime = 0x00)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(station.ToString("D2"));
            stringBuilder.Append("FF");
            stringBuilder.Append("RS");
            stringBuilder.Append(waitTime.ToString("X"));

            byte[] core = null;
            if (sumCheck)
                core = Encoding.ASCII.GetBytes(CalculateAcc(stringBuilder.ToString()));
            else
                core = Encoding.ASCII.GetBytes(stringBuilder.ToString());

            core = Utils.SpliceTwoByteArray(new byte[] { 0x05 }, core);

            return Result.CreateSuccessResult(core);
        }

        #endregion
    }
}
